/*
 * EvenEvenMachineStrategy.java
 *
 * Stateroom - Java framework library for finite state machines
 * Copyright (C) 2009 by Matthew Werny
 * All Rights Reserved
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
package com.unboundedprime.stateroom.examples.machines.fa;

import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import net.iceflow.stateroom.SimpleState;
import net.iceflow.stateroom.exception.DuplicateStateException;
import net.iceflow.stateroom.exception.UnknownStateException;
import net.iceflow.stateroom.inputprovider.QueueInputProvider;
import net.iceflow.stateroom.interfaces.Context;
import net.iceflow.stateroom.interfaces.InputProvider;
import net.iceflow.stateroom.interfaces.MachineDefinitionControls;
import net.iceflow.stateroom.interfaces.MachineStrategy;
import net.iceflow.stateroom.models.InputProviderModel;
import net.iceflow.stateroom.predicates.InputProviderPredicate;

/**
 * Defines an even-even finite automata state machine.
 */
public final class EvenEvenMachineStrategy implements MachineStrategy<InputProviderModel<String>> {
	
	/**
	 * State indicating that an even number of "a" characters and an even number of "b" characters have been processed.
	 */
	public static final SimpleState<InputProviderModel<String>> EVEN_EVEN = new SimpleState<InputProviderModel<String>>("EvenEven");
	
	/**
	 * State indicating that an even number of "a" characters and an odd number of "b" characters have been processed.
	 */
	public static final SimpleState<InputProviderModel<String>> EVEN_ODD = new SimpleState<InputProviderModel<String>>("EvenOdd");
	
	/**
	 * State indicating that an odd number of "a" characters and an odd number of "b" characters have been processed.
	 */
	public static final SimpleState<InputProviderModel<String>> ODD_ODD = new SimpleState<InputProviderModel<String>>("OddOdd");
	
	/**
	 * State indicating that an odd number of "a" characters and an even number of "b" characters have been processed.
	 */
	public static final SimpleState<InputProviderModel<String>> ODD_EVEN = new SimpleState<InputProviderModel<String>>("OddEven");
	
	/**
	 * InputProviderPredicate that evaluates as true, if the next input is an 'a'.
	 */
	static final InputProviderPredicate<String> INPUT_PROVIDER_PREDICATE_A = new InputProviderPredicate<String>("a");
	
	/**
	 * InputProviderPredicate that evaluates as true, if the next input is an 'b'.
	 */
	static final InputProviderPredicate<String> INPUT_PROVIDER_PREDICATE_B = new InputProviderPredicate<String>("b");
	
	/**
	 * Causes to the definition strategy to define the machine using the provided machine controls.
	 * @param controls Controls with which to add states and transitions
	 * @throws UnknownStateException If an unknown state is referenced
	 * @throws DuplicateStateException If an attempt is made to add a duplicate state
	 */
	@Override
	public void defineMachine(final MachineDefinitionControls<InputProviderModel<String>> controls) throws UnknownStateException, DuplicateStateException {
		// Add the machine states
		controls.addState(EVEN_EVEN, true);
		controls.addState(EVEN_ODD, false);
		controls.addState(ODD_ODD, false);
		controls.addState(ODD_EVEN, false);
		
		// Add transition edges leaving even-even state
		controls.addTransition(EVEN_EVEN, EVEN_ODD, INPUT_PROVIDER_PREDICATE_B);
		controls.addTransition(EVEN_EVEN, ODD_EVEN, INPUT_PROVIDER_PREDICATE_A);
		
		// Add transition edges leaving even-odd state
		controls.addTransition(EVEN_ODD, EVEN_EVEN, INPUT_PROVIDER_PREDICATE_B);
		controls.addTransition(EVEN_ODD, ODD_ODD, INPUT_PROVIDER_PREDICATE_A);
		
		// Add transition edges leaving odd-odd state
		controls.addTransition(ODD_ODD, ODD_EVEN, INPUT_PROVIDER_PREDICATE_B);
		controls.addTransition(ODD_ODD, EVEN_ODD, INPUT_PROVIDER_PREDICATE_A);
		
		// Add transition edges leaving odd-even state
		controls.addTransition(ODD_EVEN, ODD_ODD, INPUT_PROVIDER_PREDICATE_B);
		controls.addTransition(ODD_EVEN, EVEN_EVEN, INPUT_PROVIDER_PREDICATE_A);
	}
	
	/**
	 * Initializes the start state in the context.
	 * <p>
	 * Sets the current context to EVEN_EVEN.
	 * </p>
	 * @param context Context to populate
	 */
	@Override
	public void populateStartState(final Context<InputProviderModel<String>> context) {
		// Set the start state to be Even Even (0 a's and 0 b's to start)
		context.setCurrentState(EVEN_EVEN);
	}
	
	/**
	 * Populates the context with a model.
	 * <p>
	 * Populates the context with it's own instance of an input provider model and a queue input provider.
	 * </p>
	 * @param context AtomicContext to populate
	 */
	@Override
	public void populateContext(final Context<InputProviderModel<String>> context) {
		final Queue<String> queue = new LinkedBlockingQueue<String>();
		final InputProvider<String> provider = new QueueInputProvider<String>(queue);
                final InputProviderModel<String> model = new InputProviderModel<String>(provider);
		
		context.setModel(model);
	}

    @Override
    public boolean contextAtomicityRequired() {
        return false;
    }
}
